<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - vector - text</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				font-family: Monospace;
				background-color: #f0f0f0;
				margin: 0px;
				overflow: hidden;
			}

			#info {
				position: absolute;
				top: 10px;
				width: 100%;
				text-align: center;
			}
		</style>
	</head>
	<body>
		<div id="info">
			<a href="http://threejs.org" target="_blank">three.js</a> webgl - Resolution-Independent Vector Fonts. <a href="https://github.com/mrdoob/three.js/issues/4746">info</a>.
		</div>
		<script src="../build/three.js"></script>
		<script src="./js/controls/OrbitControls.js"></script>

		<script src="js/libs/stats.min.js"></script>

		<script type="x-shader/x-fragment" id="fs">

			varying vec2 vUv;
			varying float flip;
			uniform vec3 color;

			float inCurve(vec2 uv) {
				return uv.x * uv.x - uv.y;
			}

			float delta = 0.1;

			void main() {
				float x = inCurve(vUv);

				if (x * flip > 0.) discard;
				gl_FragColor = vec4(color, 1.);
			}

		</script>

		<script type="x-shader/x-vertex" id="vs">

			varying vec2 vUv;
			attribute float invert;
			varying float flip;

			void main() {

				vUv = uv;
				flip = invert;
				vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
				gl_Position = projectionMatrix * mvPosition;

			}

		</script>


		<script>

			var stats;

			var camera, scene, renderer, controls;

			var group, text;

			var t = false;

			function toggle() {

				if ( t ) {

					text2.visible = 0;
					text1.visible = 1;

				} else {

					text2.visible = 1;
					text1.visible = 0;

				}

				t = !t;
			}

			var loader = new THREE.FontLoader();
			loader.load( 'fonts/helvetiker_regular.typeface.json', function ( font ) {

				init( font );
				animate();

			} );

			function init( font ) {

				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 10000 );
				camera.position.set( 0, 100, 500 );

				controls = new THREE.OrbitControls( camera );
				controls.target.set( 0, 100, 0 );

				scene = new THREE.Scene();

				var theText = "&"; // i % & j b 8

				group = new THREE.Group();
				scene.add( group );


				var textMaterial = new THREE.MeshBasicMaterial( {
					color: new THREE.Color(0, 0, 1 ),
					side: THREE.DoubleSide,
					wireframe: true
				} );

				var textShapes = font.generateShapes( theText, 180, 2 );

				var text3d = new THREE.ShapeGeometry( textShapes );

				text3d.computeBoundingBox();
				var centerOffset = -0.5 * ( text3d.boundingBox.max.x - text3d.boundingBox.min.x );
				text = new THREE.Mesh( text3d, textMaterial );

				text.position.x = centerOffset - 150;
				group.add( text );

				//

				var vA = new THREE.Vector2();
				var vB = new THREE.Vector2();
				var vDot = new THREE.Vector2();

				function processShape(path, reverse) {

					var pts = []; // bigger area (convex hull)
					var pts2 = []; // smaller area (full solid shapes)
					var beziers = []; // quad bezier points
					var invert = [];
					var z;

					var wind;

					pts.push( path[0].getPoint(0) );
					pts2.push( path[0].getPoint(0) );

					for (var i=0; i < path.length; i++) {
						curve = path[i];
						if (curve instanceof THREE.LineCurve) {
							pts.push( curve.v2 );
							pts2.push( curve.v2 );
						} else if (curve instanceof THREE.QuadraticBezierCurve) {
							vA = vA.subVectors( curve.v1, curve.v0 ); // .normalize()
							vB = vB.subVectors( curve.v2, curve.v1 );
							z = vA.x * vB.y - vA.y * vB.x; // z component of cross Production
							wind = z < 0; // clockwise/anticlock wind
							// if (reverse) wind = !wind;

							// console.log(z, wind , wind ? 'clockwise' : 'anti');

							if (wind) {
								pts.push( curve.v1 );
								pts.push( curve.v2 );
								pts2.push( curve.v2 );
							} else {
								pts.push( curve.v2 );
								pts2.push( curve.v1 );
								pts2.push( curve.v2 );
							}

							var flip = wind ? 1 : -1;
							// if (reverse) flip *= -1;

							invert.push(flip, flip, flip);
							beziers.push( curve.v0, curve.v1, curve.v2);

						}
					}

					return {
						pts: pts,
						pts2: pts2,
						beziers: beziers,
						invert: invert
					};
				}

				var pts, pts2;
				var subshape;
				var convexhullShape;
				var solidShape;
				var convexhullShapeGroup = [];
				var solidShapeGroup = [];

				var beziers = [], invert = [];
				var process;
				var hole;

				for (var s=0;s<textShapes.length;s++) {

					subshape = textShapes[s];
					process = processShape(subshape.curves);

					pts = process.pts;
					pts2 = process.pts2;
					beziers = beziers.concat(process.beziers);
					invert = invert.concat(process.invert);

					convexhullShape = new THREE.Shape( pts );
					solidShape = new THREE.Shape( pts2 );

					convexhullShapeGroup.push( convexhullShape );
					solidShapeGroup.push( solidShape );

					for (var i=0; i<subshape.holes.length;i++) {
						hole = subshape.holes[i];
						// console.log('hole', hole);

						process = processShape(hole.curves, true);

						pts = process.pts;
						pts2 = process.pts2;
						beziers = beziers.concat(process.beziers);
						invert = invert.concat(process.invert);

						convexhullShape.holes.push(new THREE.Shape(pts));
						solidShape.holes.push(new THREE.Shape(pts2));

					}

				} // end of subshape

				var bezierGeometry = new THREE.Geometry();

				for (var i=0;i<beziers.length;i++) {
					p = beziers[i];
					bezierGeometry.vertices.push( new THREE.Vector3(p.x, p.y, 0) );
				}

				for (i=0;i<beziers.length;i+=3) {
					bezierGeometry.faces.push( new THREE.Face3(i, i+1, i+2) );
					bezierGeometry.faceVertexUvs[0].push( [
						new THREE.Vector2(0, 0),
						new THREE.Vector2(0.5, 0),
						new THREE.Vector2(1, 1)
					] );
				}

				text3d = new THREE.ShapeGeometry( convexhullShapeGroup );
				text3d.computeBoundingBox();
				var centerOffset = -0.5 * ( text3d.boundingBox.max.x - text3d.boundingBox.min.x );

				var text1 = new THREE.Mesh( text3d, textMaterial );

				text1.position.x = centerOffset + 150;

				group.add( text1 );

				text3d = new THREE.ShapeGeometry( solidShapeGroup );
				text3d.computeBoundingBox();
				var centerOffset = -0.5 * ( text3d.boundingBox.max.x - text3d.boundingBox.min.x );

				var text2 = new THREE.Mesh( text3d, new THREE.MeshBasicMaterial( { color: new THREE.Color(1, 0, 0 ), side: THREE.DoubleSide, wireframe: true } ) );
				text2.position.x = centerOffset + 150;
				group.add( text2 );

				//
				bezierGeometry.computeBoundingBox();
				bezierGeometry.computeFaceNormals();
				bezierGeometry.computeVertexNormals();

				bezierGeometry = new THREE.BufferGeometry().fromGeometry( bezierGeometry );
				bezierGeometry.addAttribute( 'invert', new THREE.Float32BufferAttribute( invert, 1 ) );

				//

				var newMaterial = new THREE.ShaderMaterial({
					uniforms: {
						color: { value: new THREE.Color(0.45 * 0xffffff) }
					},
					vertexShader: document.getElementById( 'vs' ).textContent,
					fragmentShader: document.getElementById( 'fs' ).textContent,
					side: THREE.DoubleSide
				});


				text = new THREE.Mesh( bezierGeometry, newMaterial );
				text.position.x = centerOffset;
				text.position.y = 0;
				text.position.z = 0;

				text.rotation.x = 0;
				text.rotation.y = Math.PI * 2;

				group.add( text );

				//


				text3d = new THREE.ShapeGeometry( solidShapeGroup );
				text3d.computeBoundingBox();

				text = new THREE.Mesh( text3d, new THREE.MeshBasicMaterial( { color: 0.45 * 0xffffff, side: THREE.DoubleSide } ) );
				text.position.x = centerOffset;
				text.position.y = 0;
				text.position.z = 0;

				text.rotation.x = 0;
				text.rotation.y = Math.PI * 2;

				group.add( text );

				//

				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setClearColor( 0xf0f0f0 );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				document.body.appendChild( renderer.domElement );

				stats = new Stats();
				document.body.appendChild( stats.dom );

				document.addEventListener( 'mousedown', toggle, false );

				window.addEventListener( 'resize', onWindowResize, false );

			}

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}


			//

			function animate() {

				requestAnimationFrame( animate );

				render();
				stats.update();

			}

			function render() {

				controls.update();
				renderer.render( scene, camera );

			}

		</script>

	</body>
</html>
